/*
  author Sylvain Bertrand <sylvain.bertrand@gmail.com>
  Protected by linux GNU GPLv2
  Copyright 2012-2014
*/
#include <linux/pci.h>
#include <linux/cdev.h>
#include <asm/unaligned.h>

#include <alga/rng_mng.h>
#include <uapi/alga/pixel_fmts.h>
#include <alga/timing.h>
#include <alga/amd/atombios/atb.h>
#include <uapi/alga/amd/dce6/dce6.h>
#include <alga/amd/atombios/vm.h>
#include <alga/amd/atombios/cm.h>
#include <alga/amd/atombios/pp.h>
#include <alga/amd/atombios/vram_info.h>

#include "../mc.h"
#include "../rlc.h"
#include "../ih.h"
#include "../fence.h"
#include "../ring.h"
#include "../dmas.h"
#include "../ba.h"
#include "../cps.h"
#include "../gpu.h"
#include "../drv.h"

#include "../regs.h"

#include "../smc_tbls.h"

#include "ctx.h"
#include "private.h"
#include "smc_eng_clk.h"
#include "smc_mem_clk.h"
#include "smc_volt.h"

#ifdef CONFIG_ALGA_AMD_SI_DYN_PM_LOG
#define L(fmt,...) printk(KERN_INFO fmt "\n", ##__VA_ARGS__)
static void dpm_to_perf_lvl_dump(struct smc_pp_dpm_to_perf_lvl *tbl)
{
	u16 tmp;
	u8 i;

	L("SMC_PP_DPM_TO_PERF_LVL START");
	L("max_pulse_skip=0x%02x",tbl->max_pulse_skip);
	L("tgt_act=0x%02x",tbl->tgt_act);
	L("max_pulse_skip_step_inc=0x%02x",tbl->max_pulse_skip_step_inc);
	L("max_pulse_skip_step_dec=0x%02x",tbl->max_pulse_skip_step_dec);
	L("ps_sampling_time=0x%02x",tbl->ps_sampling_time);
	L("near_tdp_dec=0x%02x",tbl->near_tdp_dec);
	L("above_safe_inc=0x%02x",tbl->above_safe_inc);
	L("below_safe_inc=0x%02x",tbl->below_safe_inc);
	L("ps_delta_limit=0x%02x",tbl->ps_delta_limit);
	L("ps_delta_win=0x%02x",tbl->ps_delta_win);

	tmp = get_unaligned_be16(&tbl->pwr_efficiency_ratio);
	L("pwr_efficiency_ratio=0x%04x",tmp);

	for (i = 0; i < 4; ++i)
		L("rsvd[%u]=0x%02x",i,tbl->rsvd[i]);

	L("SMC_PP_DPM_TO_PERF_LVL END");
}

void smc_lvl_dump(struct smc_lvl *lvl)
{
	u32 tmp;

	L("mc_reg_set_idx=0x%02x",lvl->mc_reg_set_idx);
	L("disp_watermark=0x%02x",lvl->disp_watermark);
	L("pcie_gen=0x%02x",lvl->pcie_gen);
	L("uvd_watermark=0x%02x",lvl->uvd_watermark);
	L("vce_watermark=0x%02x",lvl->vce_watermark);
	L("strobe_mode=0x%02x",lvl->strobe_mode);
	L("mc_flags=0x%02x",lvl->mc_flgs);
	L("pad=0x%02x",lvl->pad);

	tmp = get_unaligned_be32(&lvl->a_t);
	L("a_t=0x%08x",tmp);

	tmp = get_unaligned_be32(&lvl->b_sp);
	L("b_sp=0x%08x",tmp);

	smc_eng_clk_dump(&lvl->eng_clk);
	smc_mem_clk_dump(&lvl->mem_clk);

	L("SMC_VOLT VDDC START");
	smc_volt_dump(&lvl->vddc);
	L("SMC_VOLT VDDC END");

	L("SMC_VOLT MVDD START");
	smc_volt_dump(&lvl->mvdd);
	L("SMC_VOLT MVDDC END");

	L("SMC_VOLT VDDCI START");
	smc_volt_dump(&lvl->vddci);
	L("SMC_VOLT VDDCI END");

	L("SMC_VOLT STD_VDDC START");
	smc_volt_dump(&lvl->std_vddc);
	L("SMC_VOLT STD_VDDC END");

	L("hysteresis_up=0x%02x",lvl->hysteresis_up);
	L("hysteresis_down=0x%02x",lvl->hysteresis_down);
	L("state_flgs=0x%02x",lvl->state_flgs);
	L("mc_arb_set_idx=0x%02x",lvl->mc_arb_set_idx);

	tmp = get_unaligned_be32(&lvl->sq_pwr_throttle_0);
	L("sq_pwr_throttle_0=0x%08x",tmp);

	tmp = get_unaligned_be32(&lvl->sq_pwr_throttle_1);
	L("sq_pwr_throttle_1=0x%08x",tmp);

	tmp = get_unaligned_be32(&lvl->cus_n_max);
	L("cus_n_max=0x%08x",tmp);

	L("SMC_VOLT HIGH_TEMP_VDDC START");
	smc_volt_dump(&lvl->high_temp_vddc);
	L("SMC_VOLT HIGH_TEMP_VDDC END");

	L("SMC_VOLT LOW_TEMP_VDDC START");
	smc_volt_dump(&lvl->low_temp_vddc);
	L("SMC_VOLT LOW_TEMP_VDDC END");

	tmp = get_unaligned_be32(&lvl->rsvd[0]);
	L("rsvd[0]=0x%08x",tmp);

	tmp = get_unaligned_be32(&lvl->rsvd[1]);
	L("rsvd[1]=0x%08x",tmp);

	dpm_to_perf_lvl_dump(&lvl->dpm_to_perf_lvl);	
}
#endif

/*----------------------------------------------------------------------------*/
/* only for gddr5 */
static u8 gddr5_mem_clk_freq_ratio_compute(u32 mem_clk, u8 strobe_mode)
{
	u8 mc_para_idx;

	if (strobe_mode) {
		if (mem_clk < 12500)
			mc_para_idx = 0x00;
		else if (mem_clk > 47500)
			mc_para_idx = 0x0f;
		else
			mc_para_idx = (u8)((mem_clk - 10000) / 2500);
	} else {
		if (mem_clk < 65000)
			mc_para_idx = 0x00;
		else if (mem_clk > 135000)
			mc_para_idx = 0x0f;
		else
			mc_para_idx = (u8)((mem_clk - 60000) / 5000);
	}
	return mc_para_idx;
}

u8 gddr5_strobe_mode_compute(struct ctx *ctx, u32 mem_clk)
{
	u8 strobe_mode;
	u8 r;

	if (mem_clk <= MEM_CLK_STROBE_MODE_THRESHOLD)
		strobe_mode = 1;
	else
		strobe_mode = 0;

	r = gddr5_mem_clk_freq_ratio_compute(mem_clk, strobe_mode);

	if (strobe_mode)
		r |= SMC_STROBE_MODE_ENA;
	return r;
}

#define STROBE_MODE_ENA 1
static u8 gddr5_dll_state_compute(struct ctx *ctx, struct atb_pp_lvl *atb_lvl,
							struct smc_lvl *smc_lvl)
{
	u8 computed_ratio;
	u32 mc_seq_misc_7;
	u32 mc_seq_misc_5_or_6;
	u8 cur_ratio;

	if (!(smc_lvl->strobe_mode & SMC_STROBE_MODE_ENA))
		return 0;

	computed_ratio = gddr5_mem_clk_freq_ratio_compute(atb_lvl->mem_clk,
							STROBE_MODE_ENA);

	mc_seq_misc_7 = rr32(ctx->dev, MC_SEQ_MISC_7);
	cur_ratio = (mc_seq_misc_7 >> 16) & 0xf;

	if (computed_ratio >= cur_ratio)
		mc_seq_misc_5_or_6 = rr32(ctx->dev, MC_SEQ_MISC_5);
	else 
		mc_seq_misc_5_or_6 = rr32(ctx->dev, MC_SEQ_MISC_6);

	if (mc_seq_misc_5_or_6 & BIT(1))
		return 1;
	else
		return 0;
}
#undef STROBE_MODE_ENA
/*----------------------------------------------------------------------------*/

/*----------------------------------------------------------------------------*/
static u8 ddr3_mem_clk_freq_ratio_compute(u32 mem_clk)
{
	u8 mc_para_idx;

	if (mem_clk < 10000)
		mc_para_idx = 0;
	else if (mem_clk >= 80000)
		mc_para_idx = 0x0f;
	else
		mc_para_idx = (u8)((mem_clk - 10000) / 5000 + 1);
	return mc_para_idx;
}

static u8 ddr3_strobe_mode_compute(struct ctx *ctx, u32 mem_clk)
{
	u8 strobe_mode;
	u8 r;

	if (mem_clk <= MEM_CLK_STROBE_MODE_THRESHOLD)
		strobe_mode = 1;
	else
		strobe_mode = 0;

	r = ddr3_mem_clk_freq_ratio_compute(mem_clk);

	if (strobe_mode)
		r |= SMC_STROBE_MODE_ENA;
	return r;
}

static u8 ddr3_dll_state_compute(struct ctx *ctx, struct smc_lvl *smc_lvl)
{
	u32 mc_seq_misc_5;

	mc_seq_misc_5 = rr32(ctx->dev, MC_SEQ_MISC_5);

	if (mc_seq_misc_5 & BIT(1))
		return 1;
	else
		return 0;
}
/*----------------------------------------------------------------------------*/

static u8 strobe_mode_compute(struct ctx *ctx, u32 mem_clk)
{
	if (ctx->misc_caps & MISC_CAPS_VRAM_IS_GDDR5)
		return gddr5_strobe_mode_compute(ctx, mem_clk);
	else
		return ddr3_strobe_mode_compute(ctx, mem_clk);
}

static u8 dll_state_compute(struct ctx *ctx, struct atb_pp_lvl *atb_lvl,
							struct smc_lvl *smc_lvl)
{
	if (ctx->misc_caps & MISC_CAPS_VRAM_IS_GDDR5)
		return gddr5_dll_state_compute(ctx, atb_lvl, smc_lvl);
	else
		return ddr3_dll_state_compute(ctx, smc_lvl);
}

long smc_lvl_from_atb(struct ctx *ctx, struct atb_pp_lvl *atb_lvl,
							struct smc_lvl *smc_lvl)
{
	long r;
	u8 dll_state;

	smc_lvl->pcie_gen = pcie_speed_cap(ctx, atb_lvl->pcie_gen);

	r = smc_eng_clk_from_atb_pp(ctx, atb_lvl, smc_lvl);
	if (r == -SI_ERR) {
		dev_err(&ctx->dev->dev, "dyn_pm:unable to compute smc engine clock parameters from atombios\n");
		return -SI_ERR;
	}

	if (ctx->misc_caps & MISC_CAPS_VRAM_IS_GDDR5) {
		if (atb_lvl->mem_clk > MEM_CLK_EDC_RD_ENA_THRESHOLD)
			smc_lvl->mc_flgs |= SMC_MC_FLGS_EDC_RD;

		if (atb_lvl->mem_clk > MEM_CLK_EDC_WR_ENA_THRESHOLD)
			smc_lvl->mc_flgs |= SMC_MC_FLGS_EDC_WR;
	}

	smc_lvl->strobe_mode = strobe_mode_compute(ctx, atb_lvl->mem_clk);
	dll_state = dll_state_compute(ctx, atb_lvl, smc_lvl);

	r = smc_mem_clk_from_atb_pp(ctx, atb_lvl, smc_lvl, dll_state);
	if (r == -SI_ERR) {
		dev_err(&ctx->dev->dev, "dyn_pm:unable to compute smc memory clock parameters from atombios\n");
		return -SI_ERR;
	}

	if (ctx->volt_caps & VOLT_CAPS_VDDC_CTL_ENA) {
		r = smc_volt_vddc_set_from_atb_id(ctx, &smc_lvl->vddc,
							atb_lvl->vddc_id);
		if (r == -SI_ERR) {
			dev_err(&ctx->dev->dev, "dyn_pm:unable to find/set vddc_id\n");
			return -SI_ERR;
		}
		smc_volt_std_vddc_compute(ctx, &smc_lvl->std_vddc,
								&smc_lvl->vddc);
	} else
		LOG("no vddc control enable for atombios powerplay level");

	if (ctx->volt_caps & VOLT_CAPS_VDDC_PHASE_SHED_CTL_ENA) {
		u8 i;

		/*
		 * the limit table range from low to high volts, then locate
		 * a limit with enough power for the for the targetted vddc 
		 */
		for (i = 0; i < ctx->atb_vddc_phase_shed_limits_tbl.entries_n;
									++i) {
			struct atb_vddc_phase_shed_limits *limit;

			limit = &ctx->atb_vddc_phase_shed_limits_tbl.entries[i];
			/* don't use the atb_lvl to avoid a lkge idx */
			if (limit->vddc_mv
				>= get_unaligned_be16(&smc_lvl->vddc.val)
				&& (limit->sclk >= atb_lvl->eng_clk)
				&& (limit->mclk >= atb_lvl->mem_clk))
				break;
		}
		smc_lvl->vddc.phase_settings = i;
	} else
		LOG("no vddc phase shedding control for atombios powerplay level");

	if (ctx->volt_caps & VOLT_CAPS_VDDCI_CTL_ENA)
		smc_volt_vddci_set_from_atb_mv(ctx, &smc_lvl->vddci,
							atb_lvl->vddci_mv);
	else
		LOG("no vddci control for atombios powerplay level");

	if (ctx->volt_caps & VOLT_CAPS_MVDD_CTL_ENA) {
		smc_volt_mvdd_set_from_atb_mem_clk(ctx, &smc_lvl->mvdd,
							atb_lvl->mem_clk);
	} else
		LOG("no mvdd control for atombios powerplay level");

	smc_lvl->cus_n_max = ctx->cus_n_max;
	return 0;
}
